﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using XRMSolutions.NToNImportExport.Control.Crm;
using System.Collections.ObjectModel;
using System.IO;

namespace XRMSolutions.NToNImportExport.Control
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();

            _sfd = new SaveFileDialog()
            {
                DefaultExt = ".csv",
                Filter = "CSV Files|*.csv"
            };

            _ofd = new OpenFileDialog()
            {
                Filter = "CSV Files|*.csv",
                Multiselect = false
            };
        }

        private List<EntityMetadata> _allEntities = null;
        private List<EntityMetadata> _entities = null;
        private SaveFileDialog _sfd = null;
        private OpenFileDialog _ofd = null;
        private Dictionary<string, Guid> _firstEntityLookup = null;
        private Dictionary<string, Guid> _secondEntityLookup = null;
        private object _lock = new object();

        internal EntityMetadata FirstEntity
        {
            get
            {
                if (cmbFirstEntity.SelectedItem == null)
                    return null;

                return (EntityMetadata)cmbFirstEntity.SelectedItem;
            }
        }

        internal ManyToManyRelationshipMetadata Relationship
        {
            get
            {
                if (cmbRelationShipName.SelectedItem == null)
                    return null;

                return (ManyToManyRelationshipMetadata)cmbRelationShipName.SelectedItem;
            }
        }

        internal EntityMetadata SecondEntity
        {
            get
            {
                if (Relationship == null)
                    return null;

                EntityMetadata firstEntity = (EntityMetadata)cmbFirstEntity.SelectedItem;
                string secondEntityLogicalName = firstEntity.LogicalName.Equals(Relationship.Entity1LogicalName) ? Relationship.Entity2LogicalName : Relationship.Entity1LogicalName;

                var secondEntity = (from entity in _allEntities
                                    where entity.LogicalName == secondEntityLogicalName
                                    select entity).FirstOrDefault();

                return secondEntity;
            }
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            lblSecondEntityName.Content = string.Empty;

            IOrganizationService service = SilverlightHelper.GetSoapService();

            OrganizationRequest request = new OrganizationRequest()
            {
                RequestName = "RetrieveAllEntities"
            };

            request["EntityFilters"] = EntityFilters.Entity | EntityFilters.Relationships | EntityFilters.Attributes;
            request["RetrieveAsIfPublished"] = true;

            service.BeginExecute(request, new AsyncCallback(RetrieveEntitiesCallBack), service);
        }

        private void RetrieveEntitiesCallBack(IAsyncResult result)
        {
            OrganizationResponse response = ((IOrganizationService)result.AsyncState).EndExecute(result);

            ObservableCollection<EntityMetadata> entities = response["EntityMetadata"] as ObservableCollection<EntityMetadata>;

            this.Dispatcher.BeginInvoke(() => _allEntities = entities.ToList<EntityMetadata>());

            var Entities = (from entity in entities
                            where entity.ManyToManyRelationships.Count > 0 && 
                                entity.DisplayName.UserLocalizedLabel != null &&
                                entity.CanBeInManyToMany.Value
                            orderby entity.DisplayName.UserLocalizedLabel.Label
                            select entity).ToList<EntityMetadata>();

            this.Dispatcher.BeginInvoke(() => _entities = Entities);
            this.Dispatcher.BeginInvoke(() => cmbFirstEntity.DisplayMemberPath = "DisplayName.UserLocalizedLabel.Label");
            this.Dispatcher.BeginInvoke(() => cmbFirstEntity.ItemsSource = Entities);
            this.Dispatcher.BeginInvoke(() => cmbFirstEntity.SelectedIndex = 0);
        }

        private void cmbFirstEntity_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            cmbFirstEntityColumn.DisplayMemberPath = "DisplayName.UserLocalizedLabel.Label";
            cmbFirstEntityColumn.ItemsSource = from attr in FirstEntity.Attributes
                                               where attr.AttributeType == AttributeTypeCode.String && string.IsNullOrEmpty(attr.AttributeOf)
                                               orderby attr.DisplayName.UserLocalizedLabel.Label
                                               select attr;
            cmbFirstEntityColumn.SelectedIndex = 0;

            cmbRelationShipName.DisplayMemberPath = "SchemaName";
            cmbRelationShipName.ItemsSource = FirstEntity.ManyToManyRelationships;
            this.Dispatcher.BeginInvoke(() => cmbRelationShipName.SelectedIndex = 0);
        }

        private void cmbRelationShipName_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (Relationship == null)
                return;

            lblSecondEntityName.Content = SecondEntity.DisplayName.UserLocalizedLabel.Label;
            cmbSecondEntityColumn.DisplayMemberPath = "DisplayName.UserLocalizedLabel.Label";
            cmbSecondEntityColumn.ItemsSource = from attr in SecondEntity.Attributes
                                                where attr.AttributeType == AttributeTypeCode.String && string.IsNullOrEmpty(attr.AttributeOf)
                                                orderby attr.DisplayName.UserLocalizedLabel.Label
                                               select attr;
            cmbSecondEntityColumn.SelectedIndex = 0;
        }

        private void cbUseIds_Checked(object sender, RoutedEventArgs e)
        {
            if (cbUseIds == null)
                return;

            cmbFirstEntityColumn.IsEnabled =
            cmbSecondEntityColumn.IsEnabled = !cbUseIds.IsChecked.Value;
        }

        private void btnExport_Click(object sender, RoutedEventArgs e)
        {
            if (cmbFirstEntity.SelectedItem == null)
            {
                MessageBox.Show("Select first entity for export");
                cmbFirstEntity.Focus();
                return;
            }

            if (cmbRelationShipName.SelectedItem == null)
            {
                MessageBox.Show("Select relationship for export");
                cmbRelationShipName.Focus();
                return;
            }

            bool? dialogResult = _sfd.ShowDialog();

            if (dialogResult != true)
                return;

            FetchExpression query = new FetchExpression();

            if (cbUseIds.IsChecked.Value)
            {
                query.Query = string.Format(@"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
  <entity name='{0}'>
    <attribute name='{1}' alias='entity1id' />
    <link-entity name='{2}' from='{3}' to='{1}' link-type='inner' alias='link'>
      <attribute name='{4}' alias='entity2id' />
    </link-entity>
  </entity>
</fetch>", new object[] { FirstEntity.LogicalName, FirstEntity.PrimaryIdAttribute, Relationship.IntersectEntityName, Relationship.Entity1LogicalName == FirstEntity.LogicalName ? Relationship.Entity1IntersectAttribute : Relationship.Entity2IntersectAttribute, Relationship.Entity1LogicalName == FirstEntity.LogicalName ? Relationship.Entity2IntersectAttribute : Relationship.Entity1IntersectAttribute });
            }
            else
            {
                query.Query = string.Format(@"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
  <entity name='{0}'>
    <attribute name='{1}' alias='entity1id' />
    <link-entity name='{2}' from='{3}' to='{4}' link-type='inner' alias='link'>
     <link-entity name='{5}' from='{6}' to='{7}' link-type='inner' alias='link2'>
      <attribute name='{8}' alias='entity2id' />
     </link-entity>
    </link-entity>
  </entity>
</fetch>", new object[] { FirstEntity.LogicalName, 
                ((AttributeMetadata)cmbFirstEntityColumn.SelectedItem).LogicalName,
                Relationship.IntersectEntityName, 
                Relationship.Entity1LogicalName == FirstEntity.LogicalName ? Relationship.Entity1IntersectAttribute : Relationship.Entity2IntersectAttribute, 
                FirstEntity.PrimaryIdAttribute,
                SecondEntity.LogicalName,
                Relationship.Entity1LogicalName == FirstEntity.LogicalName ? Relationship.Entity2IntersectAttribute : Relationship.Entity1IntersectAttribute, 
                SecondEntity.PrimaryIdAttribute,
                ((AttributeMetadata)cmbSecondEntityColumn.SelectedItem).LogicalName});
            }

            IOrganizationService service = SilverlightHelper.GetSoapService();
            service.BeginRetrieveMultiple(query, new AsyncCallback(RetrieveMultipleCallBack), service);
        }

        private void RetrieveMultipleCallBack(IAsyncResult result)
        {
            EntityCollection records = ((IOrganizationService)result.AsyncState).EndRetrieveMultiple(result);

            string results = string.Empty;
            foreach (Entity entity in records.Entities)
            {
                AliasedValue entity1 = entity.Attributes.ContainsKey("entity1id") ? (AliasedValue)entity["entity1id"] : null;
                AliasedValue entity2 = entity.Attributes.ContainsKey("entity2id") ? (AliasedValue)entity["entity2id"] : null;

                results += (results != string.Empty ? Environment.NewLine : string.Empty) + GetAliasStringRepresentation(entity1) + ";" + GetAliasStringRepresentation(entity2);
            }

            this.Dispatcher.BeginInvoke(() => txtExportResults.Text = results);
            this.Dispatcher.BeginInvoke(() => { SaveFile(); });
        }

        private void SaveFile()
        {
            byte[] fileBytes = System.Text.Encoding.Unicode.GetBytes(txtExportResults.Text);

            using (Stream fs = (Stream)_sfd.OpenFile())
            {
                fs.Write(fileBytes, 0, fileBytes.Length);
                fs.Close();
            }
        }

        private string GetAliasStringRepresentation(AliasedValue alias)
        {
            if (alias == null)
                return string.Empty;
            else if (alias.Value is string)
                return alias.Value as string;
            else if (alias.Value is Guid)
                return alias.Value.ToString();
            else if (alias.Value is EntityReference)
                return ((EntityReference)alias.Value).Id.ToString();
            else
                throw new NotImplementedException();
        }

        private void btnStartImport_Click(object sender, RoutedEventArgs e)
        {
            if (cmbFirstEntity.SelectedItem == null)
            {
                MessageBox.Show("Select first entity for import");
                cmbFirstEntity.Focus();
                return;
            }

            if (cmbRelationShipName.SelectedItem == null)
            {
                MessageBox.Show("Select relationship for import");
                cmbRelationShipName.Focus();
                return;
            }

            bool? dialogResult = _ofd.ShowDialog();

            if (dialogResult != true)
                return;

            string filestring = string.Empty;

            using(StreamReader sr = new StreamReader(_ofd.File.OpenRead(), System.Text.Encoding.Unicode))
            {
                filestring = sr.ReadToEnd();
            }

            string[] lines = filestring.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);

            _firstEntityLookup = new Dictionary<string, Guid>();
            _secondEntityLookup = new Dictionary<string, Guid>();

            pbImportProgress.Minimum = 0;
            pbImportProgress.Maximum = lines.Length;

            int i = 1;

            foreach (string line in lines)
            {
                string[] parts = line.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
                if (parts.Length != 2)
                {
                    IncreaseProgress("Issues on line " + i.ToString() + " - line has " + parts.Length.ToString() + " instead of 2");
                }
                else
                {
                    string result = PrepareImportAssociation(parts[0], parts[1], i);
                    if (result != null)
                    {
                        IncreaseProgress("Issues on line " + i.ToString() + " " + result);
                    }
                }

                i++;
            }
        }

        private void IncreaseProgress(string message)
        {
            lock (_lock)
            {
                pbImportProgress.Value = pbImportProgress.Value + 1;
                if (!string.IsNullOrEmpty(message))
                    AddLogMessage(message);
            }
        }

        private void AddLogMessage(string message)
        {
            txtImportLog.Text = txtImportLog.Text + (txtImportLog.Text == string.Empty ? string.Empty : Environment.NewLine) + message;
        }

        private string PrepareImportAssociation(string part1, string part2, int linenumber)
        {
            Guid entity1Id = Guid.Empty;
            Guid entity2Id = Guid.Empty;

            if (cbUseIds.IsChecked.Value)
            {
                if (!Guid.TryParse(part1, out entity1Id))
                    return string.Format("{0} is not valid guid", part1);

                if (!Guid.TryParse(part2, out entity2Id))
                    return string.Format("{0} is not valid guid", part2);

                ImportAssociation(entity1Id, FirstEntity.LogicalName, entity2Id, SecondEntity.LogicalName, Relationship.IntersectEntityName, SilverlightHelper.GetSoapService());
            }
            else
            {
                IOrganizationService service = SilverlightHelper.GetSoapService();

                Marker marker = new Marker()
                {
                    SecondEntityLookupFieldValue = part2,
                    SecondEntityLookupFieldName = ((AttributeMetadata)cmbSecondEntityColumn.SelectedItem).LogicalName,
                    Service = service,
                    FirstEntityLogicalName = FirstEntity.LogicalName,
                    FirstEntityLookupFieldValue = part1,
                    SecondEntityLogicalName = SecondEntity.LogicalName,
                    RelationShipName = Relationship.IntersectEntityName,
                    LineNumber = linenumber
                };

                if (!_firstEntityLookup.ContainsKey(part1))
                {
                    QueryByAttribute query = new QueryByAttribute()
                    {
                        Attributes = new ObservableCollection<string>(),
                        ColumnSet = new ColumnSet()
                        {
                            Columns = new ObservableCollection<string>()
                        },
                        EntityName = FirstEntity.LogicalName,
                        Values = new ObservableCollection<object>()
                    };

                    query.Attributes.Add(((AttributeMetadata)cmbFirstEntityColumn.SelectedItem).LogicalName);
                    query.ColumnSet.Columns.Add(string.Format("{0}id", FirstEntity.LogicalName));
                    query.Values.Add(part1);

                    service.BeginRetrieveMultiple(query, new AsyncCallback(OnLookupFirstEntityCompleted), marker);
                }
                else if (!_secondEntityLookup.ContainsKey(part2))
                {
                    QueryByAttribute query = new QueryByAttribute()
                    {
                        Attributes = new ObservableCollection<string>(),
                        ColumnSet = new ColumnSet()
                        {
                            Columns = new ObservableCollection<string>()
                        },
                        EntityName = marker.SecondEntityLogicalName,
                        Values = new ObservableCollection<object>()
                    };

                    query.Attributes.Add(marker.SecondEntityLookupFieldName);
                    query.ColumnSet.Columns.Add(string.Format("{0}id", marker.SecondEntityLogicalName));
                    query.Values.Add(marker.SecondEntityLookupFieldValue);

                    marker.Service.BeginRetrieveMultiple(query, new AsyncCallback(OnLookupSecondEntityCompleted), marker);
                }
                else
                    ImportAssociation(_firstEntityLookup[part1], FirstEntity.LogicalName, _secondEntityLookup[part2], SecondEntity.LogicalName, Relationship.IntersectEntityName, service);
            }

            return null;
        }

        private void OnLookupFirstEntityCompleted(IAsyncResult asyncresult)
        {
            Marker marker = asyncresult.AsyncState as Marker;

            EntityCollection entities = marker.Service.EndRetrieveMultiple(asyncresult);
            if (entities.Entities.Count == 1)
            {
                Entity entity = entities.Entities[0];
                marker.FirstEntityId = entity.Id;

                this.Dispatcher.BeginInvoke(() => _firstEntityLookup.Add(marker.FirstEntityLookupFieldValue, entity.Id));

                if (!_secondEntityLookup.ContainsKey(marker.SecondEntityLookupFieldValue))
                {
                    QueryByAttribute query = new QueryByAttribute()
                    {
                        Attributes = new ObservableCollection<string>(),
                        ColumnSet = new ColumnSet()
                        {
                            Columns = new ObservableCollection<string>()
                        },
                        EntityName = marker.SecondEntityLogicalName,
                        Values = new ObservableCollection<object>()
                    };

                    query.Attributes.Add(marker.SecondEntityLookupFieldName);
                    query.ColumnSet.Columns.Add(string.Format("{0}id", marker.SecondEntityLogicalName));
                    query.Values.Add(marker.SecondEntityLookupFieldValue);

                    marker.Service.BeginRetrieveMultiple(query, new AsyncCallback(OnLookupSecondEntityCompleted), marker);
                }
                else
                {
                    marker.SecondEntityId = _secondEntityLookup[marker.SecondEntityLookupFieldValue];
                    ImportAssociation(marker.FirstEntityId, marker.FirstEntityLogicalName, marker.SecondEntityId, marker.SecondEntityLogicalName, marker.RelationShipName, marker.Service);
                }
            }
            else
            {
                this.Dispatcher.BeginInvoke(() => { IncreaseProgress(string.Format("Error resolving first column at line {0}", marker.LineNumber)); });
            }
        }

        private void OnLookupSecondEntityCompleted(IAsyncResult asyncresult)
        {
            Marker marker = asyncresult.AsyncState as Marker;

            EntityCollection entities = marker.Service.EndRetrieveMultiple(asyncresult);
            if (entities.Entities.Count == 1)
            {
                Entity entity = entities.Entities[0];
                ImportAssociation(marker.FirstEntityId, marker.FirstEntityLogicalName, entity.Id, marker.SecondEntityLogicalName, marker.RelationShipName, marker.Service);
            }
            else
            {
                this.Dispatcher.BeginInvoke(() => { IncreaseProgress(string.Format("Error resolving second column at line {0}", marker.LineNumber)); });
            }
        }

        private void ImportAssociation(Guid entity1Id, string entity1LogicalName, Guid entity2Id, string entity2LogicalName, string RelationshipName, IOrganizationService service)
        {
            EntityReferenceCollection references = new EntityReferenceCollection();
            references.Add(new EntityReference()
            {
                Id = entity2Id,
                LogicalName = entity2LogicalName
            });

            service.BeginAssociate(entity1LogicalName,
                entity1Id,
                new Relationship() { SchemaName = RelationshipName },
                references,
                new AsyncCallback(AssociationCreatedHandler),
                service);
        }

        private void AssociationCreatedHandler(IAsyncResult asyncresult)
        {
            try
            {
                ((IOrganizationService)asyncresult.AsyncState).EndAssociate(asyncresult);
            }
            catch { }

            this.Dispatcher.BeginInvoke(() => { IncreaseProgress(null); });
        }
    }
}
